package com.tsing.basic.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tsing.basic.entity.User;
import com.tsing.basic.mapper.RoleMapper;
import com.tsing.basic.mapper.UserMapper;
import com.tsing.basic.service.IUserService;
import com.tsing.basic.vo.req.*;
import com.tsing.basic.vo.resp.*;
import com.tsing.common.contants.JwtConstant;
import com.tsing.common.exceptions.BusinessException;
import com.tsing.common.exceptions.ServerException;
import com.tsing.common.service.BaseRedisService;
import com.tsing.common.service.UserRedisService;
import com.tsing.common.utils.BeanCoperUtil;
import com.tsing.common.utils.DateTimeUtil;
import com.tsing.common.utils.JwtUtil;
import com.tsing.common.utils.MD5Util;
import com.tsing.common.vo.UserVo;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;

/**
 * <p>
 * 服务实现类
 * </p>
 *
 * @author TheTsing
 * @since 2021-03-29
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private BaseRedisService baseRedisService;

    @Autowired
    private UserRedisService userRedisService;

    @Resource
    private RoleMapper roleMapper;

    @Override
    public UserLoginRespVo login(UserLoginReqVo reqVo) {
        UserVo userVo = saveUser2Redis(checkUP(reqVo));
        saveUserToken2Redis(userVo);
        UserLoginRespVo userLoginRespVo = new UserLoginRespVo();
        userLoginRespVo.setToken(userVo.getToken());
        return userLoginRespVo;
    }

    //账号密码校验
    private UserVo checkUP(UserLoginReqVo reqVo) {
        User user = new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId, User::getPhone, User::getPassword)
                .eq(User::getPhone, reqVo.getUsername())
                .one();
        if (null == user) {
            throw new BusinessException(ServerException.Proxy.USERNAME_FAILURE);
        }
        UserVo userVo = new UserVo();
        BeanUtils.copyProperties(user, userVo);
        if (MD5Util.verifyPassword(reqVo.getPassword(), user.getPassword(), 3)) {
            List<String> roles = baseMapper.selectRoleId(user.getId());
            if (null != roles) {
                userVo.setRoleIds(roles);
            }
        } else {
            throw new BusinessException(ServerException.Proxy.PASSWORD_FAILURE);
        }
        return userVo;
    }

    //将用户信息储存到redis
    private UserVo saveUser2Redis(UserVo userVo) {
        userVo.setToken(JwtUtil.getToken(userVo));
        userVo.setExpire(86400);
        userRedisService.saveUser2Redis(userVo);
        return userVo;
    }

    //存放与登录用户关联的token信息
    private void saveUserToken2Redis(UserVo userVo) {
        //限制账号同时只能在一处登录
//        String token = baseRedisService.getString(JwtConstant.USER_TOKEN + userVo.getId());
//        if (!StringUtils.isBlank(token)) {
//            baseRedisService.deleteKey(token);
//        }
        userRedisService.saveUserToken2Redis(userVo);
    }

    @Override
    public void logout(HttpServletRequest request) {
        baseRedisService.deleteKey(request.getHeader("token"));
    }

    @Override
    public void addUser(UserAddReqVo reqVo) {
        if (null != new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId)
                .eq(User::getPhone, reqVo.getPhone())
                .one()) {
            throw new BusinessException(ServerException.Proxy.USERNAME_IS_NOTNULL);
        }
        User user = new User();
        BeanUtils.copyProperties(reqVo, user);
        if (null != reqVo.getBirthday()) {
            user.setBirthday(DateTimeUtil.getLocalDateTimeByTimestamp(reqVo.getBirthday()));
        }
        user.setPassword(MD5Util.getMD5ByStr("admin", 3));
        baseMapper.insert(user);
    }

    @Override
    public void deleteUser(IdsReqVo reqVo) {
        for (String id : reqVo.getIds()) {
            deleteUser2Redis(id);
            deleteUser(id);
        }
    }

    private void deleteUser2Redis(String id) {
        User user = baseMapper.selectById(id);
        if (null != user) {
            String token = baseRedisService.getString(JwtConstant.USER_TOKEN + user.getId());
            if (!StringUtils.isBlank(token)) {
                baseRedisService.deleteKey(token);
            }
        }
    }

    private void deleteUser(String id) {
        baseMapper.deleteById(id);
        baseMapper.deleteRelevance(id);
    }

    @Override
    public UserRespVo getUserById(IdReqVo reqVo) {
        UserRespVo userRespVo = baseMapper.selectUserById(reqVo.getId());
        return userRespVo;
    }

    @Override
    public void updateUser(UserUpdateReqVo reqVo) {
        if (null != new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId)
                .eq(User::getPhone, reqVo.getPhone())
                .ne(User::getId, reqVo.getId())
                .one()) {
            throw new BusinessException(ServerException.Proxy.USERNAME_IS_NOTNULL);
        }
        User user = new User();
        BeanUtils.copyProperties(reqVo, user);
        if (null != reqVo.getBirthday()) {
            user.setBirthday(DateTimeUtil.getLocalDateTimeByTimestamp(reqVo.getBirthday()));
        }
        baseMapper.updateById(user);
    }

    @Override
    public void initUserPw(HttpServletRequest request) {
        UserVo userVo = userRedisService.getUserByToken(request.getHeader("token"));
        String pwd = MD5Util.getMD5ByStr("admin", 3);
        User user = new User();
        user.setId(userVo.getId());
        user.setPassword(pwd);
        baseMapper.updateById(user);
    }

    @Override
    public String passwordVerify(HttpServletRequest request, PasswordVerifyReqVo reqVo) {
        UserVo userVo = userRedisService.getUserByToken(request.getHeader("token"));
        User user = new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId, User::getPassword)
                .eq(User::getId, userVo.getId())
                .one();
        if (MD5Util.verifyPassword(reqVo.getPassword(), user.getPassword(), 3)) {
            String uuid = UUID.randomUUID().toString();
            baseRedisService.set(uuid, "1", 600L);
            return uuid;
        } else {
            throw new BusinessException(ServerException.Proxy.PASSWORD_VERIFY_FAILURE);
        }
    }

    @Override
    public void updatePassword(HttpServletRequest request, UpdatePasswordReqVo reqVo) {
        if (baseRedisService.isExistKey(reqVo.getUuid())) {
            UserVo userVo = userRedisService.getUserByToken(request.getHeader("token"));
            String pwd = MD5Util.getMD5ByStr(reqVo.getNewPassword(), 3);
            User user = new User();
            user.setId(userVo.getId());
            user.setPassword(pwd);
            baseMapper.updateById(user);
        } else {
            throw new BusinessException(ServerException.Proxy.PLEASE_PLAY_PASSWORD_VERIFY);
        }
    }

    @Override
    public Page<UserRespVo> userPage(UserPageReqVo reqVo) {
        Page<User> page = new Page<>(reqVo.getPageNum(), reqVo.getPageSize());
        List<UserRespVo> list = baseMapper.userPage(reqVo, page);
        Page<UserRespVo> userPage = new Page<>();
        userPage.setTotal(page.getTotal());//总条数
        userPage.setCurrent(page.getCurrent());//页码
        userPage.setSize(page.getSize());//当前页个数
        userPage.setRecords(list);//当前页数据
        return userPage;
    }

    @Override
    public List<RoleRespVo> getRoleById(IdReqVo reqVo) {
        return baseMapper.getRoleById(reqVo.getId());
    }

    @Override
    public List<RoleRespVo> getRoleList() {
        try {
            return BeanCoperUtil.copyObjects(roleMapper.selectList(null), RoleRespVo.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void bindRole(BindRoleReqVo reqVo) {
        String token = baseRedisService.getString(JwtConstant.USER_TOKEN + baseMapper.selectById(reqVo.getId()).getId());
        if (!StringUtils.isBlank(token)) {
            UserVo userVo = userRedisService.getUserByToken(token);
            if (null != userVo) {
                userVo.setRoleIds(new ArrayList<>());
                baseMapper.deleteRelevance(reqVo.getId());
                if (null != reqVo.getRoleId() && reqVo.getRoleId().size() != 0) {
                    baseMapper.bindRole(reqVo);
                    userVo.setRoleIds(reqVo.getRoleId());
                }
                userRedisService.saveUser2Redis(userVo);
                userRedisService.saveUserToken2Redis(userVo);
            }
        } else {
            baseMapper.deleteRelevance(reqVo.getId());
            if (null != reqVo.getRoleId() && reqVo.getRoleId().size() != 0) {
                baseMapper.bindRole(reqVo);
            }
        }
    }

    @Override
    public List<HouseTreeRespVo> getHouseTree() {
        return baseMapper.getHouseTree();
    }

    @Override
    public List<CarportTreeRespVo> getCarportTree() {
        return baseMapper.getCarportTree();
    }

    @Value("${wxMini.appid}")
    private String appid;

    @Value("${wxMini.secret}")
    private String secret;

    @Override
    public IdAndTokenRespVo miniLogin(CodeReqVo reqVo) {
        String openid = getOpenid(reqVo.getCode());
        User user = new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId)
                .eq(User::getOpenid, openid)
                .last("LIMIT 1")
                .one();
        if (ObjectUtil.isNull(user)) {
            throw new BusinessException(ServerException.Proxy.USER_NOT_EXIST);
        }
        UserVo userVo = new UserVo().setId(user.getId()).setMini(true);
        saveUser2Redis(userVo);
        saveUserToken2Redis(userVo);
        return new IdAndTokenRespVo().setId(userVo.getId()).setToken(userVo.getToken());
    }

    @Override
    @Transactional
    public IdAndTokenRespVo miniPhoneLogin(MiniPhoneLoginReqVo reqVo) {
        User user = new LambdaQueryChainWrapper<>(baseMapper)
                .select(User::getId, User::getPhone, User::getPassword)
                .eq(User::getPhone, reqVo.getPhone())
                .one();
        if (null == user) {
            throw new BusinessException(ServerException.Proxy.USERNAME_FAILURE);
        }
        if (!MD5Util.verifyPassword(reqVo.getPwd(), user.getPassword(), 3)) {
            throw new BusinessException(ServerException.Proxy.PASSWORD_FAILURE);
        }
        String openid = getOpenid(reqVo.getCode());
        new LambdaUpdateChainWrapper<>(baseMapper)
                .set(User::getOpenid, null)
                .eq(User::getOpenid, openid)
                .update();
        new LambdaUpdateChainWrapper<>(baseMapper)
                .set(User::getOpenid, openid)
                .set(User::getPhoto, reqVo.getPhoto())
                .eq(User::getPhone, reqVo.getPhone())
                .update();
        UserVo userVo = new UserVo().setId(user.getId()).setMini(true);
        saveUser2Redis(userVo);
        saveUserToken2Redis(userVo);
        return new IdAndTokenRespVo().setId(userVo.getId()).setToken(userVo.getToken());
    }

    private String getOpenid(String code) {
        HashMap<String, Object> paramMap = new HashMap<>(8);
        paramMap.put("appid", appid);
        paramMap.put("secret", secret);
        paramMap.put("js_code", code);
        paramMap.put("grant_type", "authorization_code");
        JSONObject resultObj = JSONObject.parseObject(HttpUtil.get("https://api.weixin.qq.com/sns/jscode2session", paramMap));
        String openid = resultObj.getString("openid");
        if (StrUtil.isBlank(openid)) {
            throw new BusinessException(ServerException.Proxy.WECHAT_SERVER_EXCEPTION);
        }
        return openid;
    }

}
